【Clojure入門】 Clojureの基本
前節でセットアップしたREPL環境を利用して、Clojureの基本を学びます。
Clojureのインストール
Leiningenを利用している場合、プロジェクト設定ファイル project.clj に指定したバージョンのClojureが利用可能になっているため、追加で必要な作業はありません。
REPLでClojureを対話的に試してみよう
Scalaとは大きく異なり、ClojureプログラムはREPLと接続したエディタを利用して対話的に開発していくのが一般的です。
より快適なREPL駆動開発環境については別途紹介しますが、ここではREPLを使いながらClojureに触れてみることにしましょう。
早速、先ほど準備したプロジェクトで lein rebel (または lein repl)を実行してClojure REPLを起動しましょう。
code:clojure
$ lein rebel
user=>
lein rebel の場合、以下のように入力しておくと名前空間 clojure.repl のユーティリティ(doc, source など)がREPLから利用しやすくなります。
code:clojure
nil
Hello, World!
まずは由緒正しい挨拶から。
code:clojure
user=> (println "Hello, World!")
Hello, World!
nil
(println "Hello, World!") と入力すると、挨拶のメッセージ Hello, World! に続いて nil と表示されました。
前者は関数 println の標準出力、後者は戻り値 nil (Java/Scalaにおける null に相当する値)を表しています。
Scalaの println("Hello, World!") に対して一見して分かる明らかな違いは括弧の位置でしょう。
Clojureを含むLisp系言語では、関数呼び出し(関数適用)を 関数名(引数 ...) ではなく (関数名 引数 ...) という形式で書きます。
関数名、引数間の区切りはスペースや改行など空白文字で表されるため、カンマ , も不要です(Clojureでは可読性のために , を使うこともできますが、空白文字扱いされます: e.g. [1,2,3] = [1 2 3])。
次に、関数呼び出しの (println ) の部分を外してみましょう。
code:clojure
user=> "Hello, World!"
"Hello, World!"
入力したものと同じ値 "Hello, World!" が表示されました。
REPLで何かを入力すると、その名の通り read(読み取り)され、 eval(評価)され、結果が print(印字)されます。
ここでは文字列リテラルを評価した結果がそのまま文字列として表示されています。
このときの結果値の型を確認するには (type *1) と入力します。
code:clojure
user=> (type *1) ; (type "Hello, World!") と等価
java.lang.String
type は引数として与えられた値の型を返す関数で、 *1 はREPLにおいて直前に印字された値を保持する変数です。
したがって、先ほどの "Hello, World!" が String 型であることが分かります。
練習問題1
様々なリテラルをREPLで評価してみましょう。
また、 type で値の型を確認してみましょう。
どのように表示されるでしょうか?
0xff
1e308
9223372036854775807
9223372036854775808
9223372036854775807N
922337203685477580.7
"\u3042"
"\ud842\udf9f"
簡単な計算
次は簡単な数値計算をしてみましょう。
code:clojure
user=> (+ 1 2)
3
user=> (type *1)
java.lang.Long
1 + 2の結果が3であり、 Long 型の値であることが分かります(Scalaとは異なり、Clojureでサフィックスのない数値リテラルはデフォルトで Long 型になります)。
Lispでは、他言語で二項の中置演算子とされることの多い加減乗除などの演算子も関数として一貫して (関数名 引数 ...) という記法で扱います。
二項演算ではなく可変長引数をとるように自然に拡張されているので、例えば1 + 2 + 3 + 4 + 5は次のように計算できます。
code:clojure
user=> (+ 1 2 3 4 5)
15
+ のほかに -, *, / などの関数もあり、次のように利用することができます。
code:clojure
user=> (+ 1 2)
3
user=> (* 2 2)
4
user=> (/ 4 2)
2
user=> (/ 4 3)
4/3
user=> (type *1)
clojure.lang.Ratio
user=> (quot 4 3)
1
user=> (mod 4 3)
1
Clojureの / 関数は整数で割り切れない場合に整数の商ではなく分数(有理数)の Ratio 型の値を返すことに注意が必要です(商を求めるために"quotient"の quot 関数があります)。
また、剰余演算の % 関数はなく、"modulo"の mod (または"remainder"の rem)を利用します。
浮動小数点数の Double 型の値についても同様に数値計算することができます。
小数部を切り捨てて整数型にキャストするには long, int などの関数を利用します。
code:clojure
user=> (+ 1.0 2.0)
3.0
user=> (* 2.2 2)
4.4
user=> (/ 4.5 2)
2.25
user=> (mod 4 3)
1
user=> (mod 4.0 2.0)
0.0
user=> (mod 4.0 3.0)
1.0
user=> (long 4.5)
4
user=> (long 4.0)
4
練習問題2
+, -, *, / を使った数式を評価してみましょう。
また、 type で値の型を確認してみましょう。
(+ (int 2147483647) 1)
(+ 9223372036854775807 1)
(+ 1e308 1)
(+ 1 0.0000000000000000001)
(- 1 1)
(- 1 0.1)
(- 0.1 1)
(- 0.1 0.1)
(- 0.0000000000000000001 1)
(* 0.1 0.1)
(* 20 0.1)
(/ 1 3)
(/ 1.0 3)
(/ 1 3.0)
(/ 3.0 3.0)
(/ (* (/ 1.0 10) 1) 10)
(/ (* (/ 1 10) 1) 10.0)
変数の基本
ここまでの例では文字列や数値のリテラルを直接入力してきましたが、プログラミングにおいて変数が扱えると便利です。
ClojureにはJavaやScalaなどに見られる代入演算子 = はありません(むしろ = は比較演算の関数です)。
defによるトップレベル変数定義
現在の名前空間にトップレベルに変数を定義するには def という特殊形式(special form; 組み込みオペレータ)を利用します。
code:clojure
user=> (def x (* 3 2))
user=> x
6
(def <名前> <値>) という形式で、ここでは現在の名前空間 user に変数 x を定義し、 (* 3 2) の評価結果を格納しています。
def で定義される変数は原則として変更できません(特殊な方法で変更する手段も一応用意されていますが普段使いするものではありません)。
Scalaが val と var を提供しているのとは異なり、再代入に相当する手段が基本的に存在しないので、 def で定義されたものは定数と考えて良いでしょう。
Clojureは動的型付けの言語なので、JavaやScalaと同じような意味での変数の型宣言や型推論といった仕組みはありません。
letによるローカル変数束縛
Clojureでは、名前空間のトップレベルに変数を定義する以外に局所的(ローカル)に利用可能な変数を導入することもできます。
特殊形式 let は (let [<名前> <値>] <本体>) という形式で名前(シンボル)に値を束縛した状態で本体を評価します。
code:clojure
36
あくまで let のレキシカルスコープ内でローカルに利用可能な変数なので、外部で n を評価しても(別途 def などで定義されていなければ)エラーになります。
code:clojure
user=> n
Syntax error compiling at (form-init7804015141232565469.clj:1:165).
Unable to resolve symbol: n in this context
let は名前と値のペアを複数まとめて束縛することが可能なので、次のような使い方もできます。
code:clojure
user=> (let [x 2
24
関数の内部など、局所的にその場限りで必要な変数には let を利用すると良いでしょう。
練習問題3
数値に対する演算と変数を利用して複雑な計算をしてみましょう。
Q. ¥3,950,000を年利率2.3%の単利で8か月間借り入れた場合の利息はいくらか? (円未満切り捨て)
Q. 定価¥1,980,000の商品を値引きして販売したところ、原価の1.6%にあたる¥26,400の損失となった。割引額は定価の何%にあたるか?